package p20_mvc初体验;

import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 上一节对DispatcherServlet的创建和初始化进行了介绍
 * 这一节介绍DispatcherServlet的初始化到底干了什么事
 * 无非就是初始化了一系列组件，这节介绍其中最重要的两个组件:
 *    1.处理器映射器
 *    2.处理器适配器
 */
public class _2_DispatcherServlet初始化干了什么 {
    public static void main(String[] args) throws Exception {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(_1_DispatcherServlet初始化时机.class);
        /**
         * 通过上一节可知，初始化本质就是执行DispatcherServlet的onRefresh()方法
         * 而onRefresh()方法，会依次执行下列代码
         *    this.initMultipartResolver(context); -> 初始化文件上传解析器
         *    this.initLocaleResolver(context); -> 初始化国际化信息
         *    this.initThemeResolver(context); -> 不重要
         *    this.initHandlerMappings(context); -> 初始化处理器映射器。会根据请求uri在容器中找处理器，没找到就使用默认的
         *    this.initHandlerAdapters(context); -> 初始化处理器适配器，根据请求适配不同的控制器方法，并调用控制器方法
         *    this.initHandlerExceptionResolvers(context); -> 初始化控制器异常解析器
         *    this.initRequestToViewNameTranslator(context);
         *    this.initViewResolvers(context);
         *    this.initFlashMapManager(context);
         * 上面每一步方法，都会初始化一个(系列)组件，如果容器中没有，则使用默认的
         * 其中最重要的几个方法是:
         *    1.initHandlerMappings(context) - 这节介绍
         *    2.initHandlerAdapters(context) -这节介绍
         *    3.initHandlerExceptionResolvers(context)
         */

        /**
         * 处理器映射器，在容器注入了一个RequestMappingHandlerMapping，优先使用我们注入的
         * 他的作用:解析@RequestMapping注解及其派生注解，生成路径与控制器方法的映射关系(初始化处理器映射器时就生成)
         * 这里测试的控制器方法来自本目录下的MyController类型
         */
        RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        /**
         * 控制器方法与请求路径的映射关系由处理器映射器持有，下面方法可以获取映射关系
         * RequestMappingInfo:封装了请求路径
         * HandlerMethod:封装了控制器的方法
         */
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
        handlerMethods.forEach((k, v) -> {
            System.out.println(k + "::" + v);
        });
        /**
         * 请求来了，是如何根据请求对象获取要对应控制器方法的呢？
         * RequestMappingHandlerMapping提供了方法，可以由请求对象得到HandlerMethod对象
         * 当然，得到的HandlerMethod是被进一步封装的HandlerExecutionChain对象
         *    因为目标控制器方法可能有一系列拦截器，所以需要封装成一个执行链对象
         * MockHttpServletRequest:可以模拟请求
         */
        MockHttpServletRequest req = new MockHttpServletRequest("POST", "/test");
        req.setParameter("name", "狗剩");
        HandlerExecutionChain chain = handlerMapping.getHandler(req);

        /**
         * 目标控制器方法及其一系列拦截器都获取到了，那么如何调用呢？
         * 调用需要用到另一个组件:处理器适配器
         * 处理器适配器跟处理器映射器一样，容器中有使用容器中的，没有使用默认的
         */
        RequestMappingHandlerAdapter handlerAdapter = context.getBean(RequestMappingHandlerAdapter.class);
        MockHttpServletResponse response = new MockHttpServletResponse();
        //handler最终调用到invokeHandlerMethod()，invokeHandlerMethod又最终调用到控制器方法
        handlerAdapter.handle(req, response, chain.getHandler());

        /**
         * 请求中的url参数：name会被解析出来，赋值给处理器方法的name参数
         * 参数的解析由专门的组件:参数解析器，来解析，不同类型的处理器参数也有不同的参数解析器
         * 下一节介绍参数解析器
         */
        context.close();
    }

    @Configuration
    static class WebConfig {
        /**
         * 这节为了方便演示处理器映射器，需要手动注入一个处理器映射器
         * 一旦容器中有处理器映射器，DispatcherServlet初始化时就会使用这个
         * 如果使用默认的处理器映射器，它会把处理器映射器作为DispatcherServlet的一个成员变量，不好测试
         * 处理器映射器初始化时会在容器中扫描所有控制器bean(@Controller修饰的bean)，
         * 并看其内部的方法是否被@RequestMapping注解及其派生注解，有的话就将该方法封装成一个HandlerMethod并与请求路径建立映射关系，然后由处理器保存起来
         */
        @Bean
        public RequestMappingHandlerMapping requestMappingHandlerMapping() {
            return new RequestMappingHandlerMapping();
        }

        /**
         * 处理器适配器
         */
        @Bean
        public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
            RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
            //给RequestMappingHandlerAdapter添加自定义参数解析器
            List<HandlerMethodArgumentResolver> list = new ArrayList<>();
            list.add(new _3_参数解析器.RemoteIpArgResolver());
            adapter.setCustomArgumentResolvers(list);

            //给RequestMappingHandlerAdapter添加返回值处理器
            List<HandlerMethodReturnValueHandler> list1 = new ArrayList<>();
            list1.add(new _4_返回值解析器.YmlReturnValueHandler());
            adapter.setCustomReturnValueHandlers(list1);
            return adapter;
        }
    }
}
